﻿using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using Creventive.Wpf.ShellFactory.Services;
using Creventive.Wpf.ShellFactory.Utility;

namespace Creventive.Wpf.ShellFactory.Workspace.Primitives
{
    /// <summary>
    ///   This is the base class of all application views in a ShellFactory application.
    /// </summary>
    public abstract class ApplicationView : ContentControl
    {
        #region Statics

        private static readonly DependencyPropertyKey IsActivePropertyKey = DependencyProperty.RegisterReadOnly("IsActive", typeof(bool), typeof(ApplicationView), new UIPropertyMetadata(false));

        /// <summary>
        ///   Backing field for the IsActive dependency property
        /// </summary>
        public static readonly DependencyProperty IsActiveProperty = IsActivePropertyKey.DependencyProperty;

        /// <summary>
        ///   Backing field for the Icon dependency property
        /// </summary>
        public static readonly DependencyProperty IconProperty = DependencyProperty.Register("Icon", typeof(ImageSource), typeof(ApplicationView), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, (s, e) => ((ApplicationView)s).OnIconChanged((ImageSource)e.OldValue, (ImageSource)e.NewValue)));

        /// <summary>
        ///   Backing field for the Title dependency property
        /// </summary>
        public static readonly DependencyProperty TitleProperty = DependencyProperty.Register("Title", typeof(object), typeof(ApplicationView), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, (s, e) => ((ApplicationView)s).OnTitleChanged(e.OldValue, e.NewValue)));

        static ApplicationView()
        {
            FocusableProperty.OverrideMetadata(typeof(ApplicationView), new FrameworkPropertyMetadata(false));
            IsTabStopProperty.OverrideMetadata(typeof(ApplicationView), new FrameworkPropertyMetadata(false));
        }

        /// <summary>
        ///   Loads an application view from the given URI.
        /// </summary>
        /// <param name = "uri">The URI.</param>
        /// <returns>The application view at the given URI</returns>
        public static ApplicationView FromUri(Uri uri)
        {
            if (uri == null)
                throw new ArgumentNullException("uri");
            var view = Application.LoadComponent(uri) as ApplicationView;
            if (view == null)
                throw new InvalidOperationException("Source URI does not point to an object of type " + typeof(ApplicationView).FullName);
            return view;
        }

        /// <summary>
        ///   Loads an application view from the given URI.
        /// </summary>
        /// <typeparam name = "T">The expected type of the application view.</typeparam>
        /// <param name = "uri">The URI.</param>
        /// <returns>The application view at the given URI</returns>
        public static T FromUri<T>(Uri uri) where T : ApplicationView
        {
            return (T)Application.LoadComponent(uri);
        }

        /// <summary>
        ///   Finds the parent view of the given item.
        /// </summary>
        /// <param name = "item">The item.</param>
        /// <returns></returns>
        public static ApplicationView FindParentView(DependencyObject item)
        {
            var parent = VisualTreeHelper.GetParent(item) ?? LogicalTreeHelper.GetParent(item);
            while (parent != null)
            {
                if (parent is ApplicationView)
                    return (ApplicationView)parent;
                parent = VisualTreeHelper.GetParent(item) ?? LogicalTreeHelper.GetParent(item);
            }
            return null;
        }

        /// <summary>
        ///   Finds the parent workspace of the view the given item resides in.
        /// </summary>
        /// <param name = "item">The item.</param>
        /// <returns></returns>
        public static IWorkspace FindParentWorkspace(DependencyObject item)
        {
            var parent = VisualTreeHelper.GetParent(item) ?? LogicalTreeHelper.GetParent(item);
            while (parent != null)
            {
                if (parent is IWorkspace)
                    return (IWorkspace)parent;
                parent = VisualTreeHelper.GetParent(item) ?? LogicalTreeHelper.GetParent(item);
            }
            return null;
        }

        #endregion

        private bool isPrimaryLoad = true;
        private IServiceContainer services;
        private WorkspaceItem workspaceItem;

        /// <summary>
        ///   Initializes a new instance of the <see cref = "ApplicationView" /> class.
        /// </summary>
        protected ApplicationView()
        {
            this.Loaded += this.ApplicationView_Loaded;
            this.services = ServiceContainer.Default.CreateChild();
            this.services.RegisterService(this, ServiceLifetime.ObjectLifetime);
        }

        /// <summary>
        ///   Occurs when this view has been closed.
        /// </summary>
        public event EventHandler Closed;

        /// <summary>
        ///   Occurs when this view is asked if it can close. Be aware that an actual close may be cancelled at other locations of the application even if this view accepts the close request.
        /// </summary>
        public event CancelEventHandler Closing;

        /// <summary>
        ///   Occurs when the view has opened (i.e. the first time the Loaded event fires)
        /// </summary>
        public event EventHandler Opened;

        /// <summary>
        ///   Occurs when the view model has been loaded.
        /// </summary>
        public event EventHandler ViewModelLoaded;

        /// <summary>
        ///   Gets or sets the icon representing this view.
        /// </summary>
        /// <value>The icon.</value>
        public ImageSource Icon
        {
            get { return (ImageSource)this.GetValue(IconProperty); }
            set { this.SetValue(IconProperty, value); }
        }

        /// <summary>
        ///   Gets or value indicating whether this view is active.
        /// </summary>
        /// <value><c>true</c> if this instance is active; otherwise, <c>false</c>.</value>
        public bool IsActive
        {
            get { return (bool)this.GetValue(IsActiveProperty); }
            private set { this.SetValue(IsActivePropertyKey, value); }
        }

        /// <summary>
        ///   Gets the application services available for this view. Be aware that these services may be an extension of the global service container.
        /// </summary>
        /// <value>The services.</value>
        public IServiceContainer Services
        {
            get { return this.services; }
        }

        /// <summary>
        ///   Gets or sets the title of this view.
        /// </summary>
        /// <value>The title.</value>
        public object Title
        {
            get { return this.GetValue(TitleProperty); }
            set { this.SetValue(TitleProperty, value); }
        }

        /// <summary>
        ///   Gets the view model of this view.
        /// </summary>
        /// <value>The view model.</value>
        public ApplicationViewModel ViewModel
        {
            get { return this.DataContext as ApplicationViewModel; }
        }

        /// <summary>
        ///   Gets the data type of the view model used by this view.
        /// </summary>
        /// <value>The type of the view model.</value>
        public abstract Type ViewModelType { get; }

        /// <summary>
        ///   Gets the workspace item this view is attached to.
        /// </summary>
        /// <value>The workspace item.</value>
        protected WorkspaceItem WorkspaceItem
        {
            get { return this.workspaceItem; }
        }

        /// <summary>
        ///   Activates this view.
        /// </summary>
        /// <returns></returns>
        public bool Activate()
        {
            if (this.WorkspaceItem != null)
                return this.WorkspaceItem.Workspace.Activate(this);
            else
                return false;
        }

        /// <summary>
        ///   Called when the Icon property has changed.
        /// </summary>
        /// <param name = "oldValue">The old value.</param>
        /// <param name = "newValue">The new value.</param>
        protected virtual void OnIconChanged(ImageSource oldValue, ImageSource newValue)
        { }

        /// <summary>
        ///   Called when the Title property has changed.
        /// </summary>
        /// <param name = "oldValue">The old value.</param>
        /// <param name = "newValue">The new value.</param>
        protected virtual void OnTitleChanged(object oldValue, object newValue)
        { }

        /// <summary>
        ///   Raises the <see cref = "E:System.Windows.FrameworkElement.Initialized" /> event. This method is invoked whenever <see cref = "P:System.Windows.FrameworkElement.IsInitialized" /> is set to true internally.
        /// </summary>
        /// <param name = "e">The <see cref = "T:System.Windows.RoutedEventArgs" /> that contains the event data.</param>
        protected override void OnInitialized(EventArgs e)
        {
            // View models cannot be constructed in design mode.
            if (!DesignerProperties.GetIsInDesignMode(this))
            {
                this.DataContext = ApplicationViewModelFactory.Create(this);
                this.OnViewModelLoaded();
            }
            base.OnInitialized(e);
        }

        /// <summary>
        ///   Called when the view model has been loaded.
        /// </summary>
        protected virtual void OnViewModelLoaded()
        {
            var handler = this.ViewModelLoaded;
            if (handler != null)
                handler.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        ///   Occurs when the view has opened (i.e. the first time the Loaded event fires)
        /// </summary>
        protected virtual void OnOpened()
        {
            EventHandler handler = this.Opened;
            if (handler != null)
                handler.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        ///   Called when the view has been closed.
        /// </summary>
        protected virtual void OnClosed()
        {
            if (this.ViewModel != null)
                this.ViewModel.Close();
            EventHandler handler = this.Closed;
            if (handler != null)
                handler.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        ///   Called when the view asks to be closed.
        /// </summary>
        /// <param name = "e">The <see cref = "System.ComponentModel.CancelEventArgs" /> instance containing the event data.</param>
        protected virtual void OnClosing(CancelEventArgs e)
        {
            CancelEventHandler handler = this.Closing;
            if (handler != null)
                handler.Invoke(this, e);
            if (!e.Cancel)
            {
                if (this.ViewModel != null)
                    e.Cancel = !this.ViewModel.CanClose();
            }
        }

        /// <summary>
        ///   Called when this view has been activated by the owner workspace.
        /// </summary>
        protected virtual void OnActivated()
        {
            this.IsActive = true;
        }

        /// <summary>
        ///   Called when this view has been deactivated by the owner workspace.
        /// </summary>
        protected virtual void OnDeactivated()
        {
            this.IsActive = false;
        }

        internal void NotifyActivated()
        {
            this.OnActivated();
        }

        internal void NotifyDeactivated()
        {
            this.OnDeactivated();
        }

        internal void NotifyClose()
        {
            this.OnClosed();
        }

        internal bool QueryCanClose()
        {
            CancelEventArgs e = new CancelEventArgs();
            this.OnClosing(e);
            return !e.Cancel;
        }

        /// <summary>
        /// Invoked when the parent of this element in the visual tree is changed. Overrides <see cref="M:System.Windows.UIElement.OnVisualParentChanged(System.Windows.DependencyObject)"/>.
        /// </summary>
        /// <param name="oldParent">The old parent element. May be null to indicate that the element did not have a visual parent previously.</param>
        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            var workspaceItem = this.FindVisualAncestor<WorkspaceItem>();
            if (workspaceItem != this.workspaceItem)
            {
                this.workspaceItem = workspaceItem;
                this.OnWorkspaceItemChanged();
            }
            base.OnVisualParentChanged(oldParent);
        }

        /// <summary>
        /// Called when the view's workspace item has changed.
        /// </summary>
        protected virtual void OnWorkspaceItemChanged()
        { }

        private void ApplicationView_Loaded(object sender, RoutedEventArgs e)
        {
            if (this.isPrimaryLoad)
            {
                this.isPrimaryLoad = false;
                if (this.ViewModel != null)
                    this.ViewModel.Load();
                this.OnOpened();
            }
        }
    }
}